import VarGlobales
import random
import time

class Node:
    
    def accept(self, visitor):
        return self._accept(self.__class__, visitor)
        
    def _accept(self, klass, visitor):
        visitor_method = getattr(visitor, "v%s" % klass.__name__, None)
        if visitor_method == None:
            bases = klass.__bases__
            last = None
            for i in bases:
                last = self._accept(i, visitor)
            return last
        else:
            return visitor_method(self)

class Programa(Node):
    def __init__(self, lista):
        self.lista = lista
        
    def ejecutar(self, id, parametros):
        self.lista.ejecutar(id, parametros)
        
class Programa2(Node):
    def __init__(self, lista):
        self.lista = lista
        
    def ejecutar(self):
        self.lista.ejecutar()

class Lista(Node):
    def __init__(self, nodo = None):
        self.lista = []
        if nodo != None:
            self.add(nodo)
            
    def add(self, nodo):
        self.lista.append(nodo)
        
    def ejecutar(self):
        rb = None
        for i in self.lista:
            rb = i.ejecutar()
            if rb != None and not(VarGlobales.retorno):
                break
        return rb
    
class ListaInstruciones(Lista):
    def __init__(self, nodo = None):
        Lista.__init__(self, nodo)
            
    
class ListaParametros(Lista):
    def __init__(self, nodo = None):
        Lista.__init__(self, nodo)
        
    def ejecutar(self):
        params = []
        for i in self.lista:
            params.append(i.ejecutar())
        return params
    
class ListaArgumentos(Lista):
    def __init__(self, nodo = None):
        Lista.__init__(self, nodo)
    
class ListaFunciones(Lista):
    def __init__(self, nodo = None):
        Lista.__init__(self, nodo)
        
    def ejecutar(self, id, parametros):
        for i in self.lista:
            if i.id.nombre == id:
                if (parametros == i.ListaArgumentos) or len(parametros) == len(i.ListaArgumentos.lista):
                    if parametros != None:
                        for j in range(len(parametros)):
                            VarGlobales.PilaVariables.addVar(i.ListaArgumentos.lista[j].nombre,parametros[j].tipo,parametros[j].valor)
                    return i.ejecutar()
                else:
                    raise RuntimeError("Los parametros del llamado no coinciden")
        raise RuntimeError("La funcion no ha sido declarada")
        
class procedimiento(Node):
    def __init__(self, id, ListaArgumentos, bloque):
        self.id = id
        self.ListaArgumentos = ListaArgumentos
        self.bloque = bloque
        
    def ejecutar(self):
        return self.bloque.ejecutar()
        
class bloque(Node):
    def __init__(self, bloque):
        self.bloque = bloque
        
    def ejecutar(self):
        return self.bloque.ejecutar()

class InstruccionNormal(Node):
    def __init__(self, tipo, expr, linea = 0):
        self.tipo = tipo
        self.expr = expr
        self.linea = int(linea)
        
    def ejecutar(self):
        e = self.expr.ejecutar()
        if not (e.tipo in [0,1]) and not(self.tipo == 'escribe'):
            raise RuntimeError("Linea %d: Expresion no valida para la instruccion" % (0 if self.linea - VarGlobales.Lresta < 0 else self.linea - VarGlobales.Lresta))
        time.sleep(VarGlobales.waittime)
        if self.tipo in['av','avanza']:
            VarGlobales.canvas.Move(float(e.valor),0)
            #time.sleep(VarGlobales.waittime)
            #VarGlobales.nlineas+=1
            #VarGlobales.listal.append(VarGlobales.nlineas)
        elif self.tipo in['rt','retroceder']:
            VarGlobales.canvas.Move(float(e.valor),1)
            #time.sleep(VarGlobales.waittime)
        elif self.tipo in['gd','girarderecha']:
            VarGlobales.canvas.TurnRight(float(e.valor))
        elif self.tipo in['gi','girarizquierda']:
            VarGlobales.canvas.TurnLeft(float(e.valor))
            #pass
        elif self.tipo in['escribe']:
            if e.tipo != 2:
                VarGlobales.txtconsola.AppendText(str(e.valor)+"\n")
            else:
                if e.valor == True:
                    VarGlobales.txtconsola.AppendText(str("verdadero")+"\n")
                else:
                    VarGlobales.txtconsola.AppendText(str("falso")+"\n")
            #print e.valor
    
class InstruccionSimple(Node):
    def __init__(self, tipo):
        self.tipo=tipo
       
    def ejecutar(self):
        if self.tipo in ['borrarpantalla','bo']:
            VarGlobales.canvas.RestartPos()
        elif self.tipo in ['bajarpluma','bp']:
            VarGlobales.canvas.SetTouch(1)
        elif self.tipo in ['subirpluma','sp']:
            VarGlobales.canvas.SetTouch(0)
        elif self.tipo in ['alto','al']:
            VarGlobales.retorno = False
            return Variable(None, 0, 1)
    
class LLamada(Node):
    def __init__(self, id, parametros = None, linea = 0):
        self.id = id
        self.parametros = parametros
        self.linea = int(linea)
        
    def ejecutar(self):
        if self.parametros != None:
            lp = self.parametros.ejecutar()
        else:
            lp = []
        VarGlobales.PilaVariables.reservarVars()
        for i in VarGlobales.ListaFunciones:
            if self.id.nombre == i.id.nombre:
                if i.ListaArgumentos != None:
                    if len(lp) == len(i.ListaArgumentos.lista):
                            for j in range(len(lp)):
                                VarGlobales.PilaVariables.addVar(i.ListaArgumentos.lista[j].nombre, lp[j].tipo,lp[j].valor)
                            r = i.bloque.ejecutar()
                            VarGlobales.PilaVariables.popVars()
                            VarGlobales.retorno = True
                            return r
                        
                    else:
                        raise RuntimeError("Linea %d: Los parametros del llamado no coinciden con los de la funcion" % (0 if self.linea - VarGlobales.Lresta < 0 else self.linea - VarGlobales.Lresta))
                else:
                    if self.parametros == None:
                        r = i.bloque.ejecutar()
                        VarGlobales.PilaVariables.popVars()
                        VarGlobales.retorno = True
                        return r
                    else:
                        raise RuntimeError("Linea %d: Los parametros del llamado no coinciden con los de la funcion" % (0 if self.linea - VarGlobales.Lresta < 0 else self.linea - VarGlobales.Lresta))
        raise RuntimeError("Linea %d: La funcion no ha sido declarada" % (0 if self.linea - VarGlobales.Lresta < 0 else self.linea - VarGlobales.Lresta))

class InstruccionSI(Node):
    def __init__(self, condicion = None, bloque1 = None, bloque2 = None, linea = 0):
        self.condicion = condicion
        self.bloque1 = bloque1
        self.bloque2 = bloque2
        self.linea = int(linea)
        
    def ejecutar(self):
        c = self.condicion.ejecutar()
        if c.tipo == 2:
            if c.valor == True:
                return self.bloque1.ejecutar()
            else:
                if self.bloque2 != None:
                    return self.bloque2.ejecutar()
        else:
            raise RuntimeError("Linea %d: La expresion debe ser booleana en la condicion" % (0 if self.linea - VarGlobales.Lresta < 0 else self.linea - VarGlobales.Lresta))
    
class CicloRepetir(Node):
    def __init__(self,expr, bloque, linea = 0):
        self.expr = expr
        self.bloque = bloque
        self.linea = int(linea)
        
    def ejecutar(self):
        e = self.expr.ejecutar()
        rb = None
        if e.tipo == 0:
            for i in range(e.valor):
                rb = self.bloque.ejecutar()
                if rb != None:
                    break
            return rb
        else:
            raise RuntimeError("Linea %d: La expresion en el ciclo debe ser entera" % (0 if self.linea - VarGlobales.Lresta < 0 else self.linea - VarGlobales.Lresta))  

class CicloPara(Node):
    def __init__(self, id, expr1, hasta, incremento, bloque, linea = 0):
        self.id = id
        self.expr1 = expr1##DESDE
        self.hasta = hasta##HASTA
        self.incremento = incremento##INCREMENTO
        self.bloque = bloque
        self.linea = int(linea)
        
    #DUDA CON EL AMBITO DE id y hasta donde
    def ejecutar(self):
        e1 = self.expr1.ejecutar()
        e2 = self.hasta.ejecutar()
        inc = self.incremento.ejecutar()
        VarGlobales.PilaVariables.addVar(self.id.nombre,e1.tipo,e1.valor)
        rb = None
        if e1.tipo == 0 and e2.tipo == 0 and inc.tipo == 0:
            for i in range(e1.valor, e2.valor+1, inc.valor):
                VarGlobales.PilaVariables.addVar(self.id.nombre,e1.tipo,i)
                rb = self.bloque.ejecutar()
                if rb != None:
                    break
            return rb
        else:
            raise RuntimeError("Linea %d: Solo se permiten valores enteros en las expresiones del ciclo" % (0 if self.linea - VarGlobales.Lresta < 0 else self.linea - VarGlobales.Lresta))
    
class InstruccionMientras(Node):
    def __init__(self, condicion, bloque, linea = 0):
        self.condicion = condicion
        self.bloque = bloque
        self.linea = int(linea)
        
    def ejecutar(self):
        c = self.condicion.ejecutar()
        rb = None
        if c.tipo == 2:
            while c.valor:
                rb = self.bloque.ejecutar()
                if rb != None:
                    break
                c = self.condicion.ejecutar()
                if c.tipo != 2:
                    raise RuntimeError("Linea %d: Expresion no Booleana" % (0 if self.linea - VarGlobales.Lresta < 0 else self.linea - VarGlobales.Lresta))
        else:
            raise RuntimeError("Linea %d: Expresion no Booleana" % (0 if self.linea - VarGlobales.Lresta < 0 else self.linea - VarGlobales.Lresta))
        return rb

class Asignacion(Node):
    def __init__(self,id,expr):
        self.id = id
        self.expr=expr
        
    def ejecutar(self):
        e = self.expr.ejecutar()
        if e != None:
            VarGlobales.PilaVariables.addVar(self.id.nombre,e.tipo,e.valor)
        else:
            VarGlobales.PilaVariables.addVar(self.id.nombre,3,None)
        
class Retorno(Node):        
    def __init__(self,expr):
        self.expr = expr
        
    def ejecutar(self):
        VarGlobales.retorno = False
        return self.expr.ejecutar()

class OpBinaria(Node):
    def __init__(self, Op, izq = None, der = None, linea = 0):
        self.Op = Op
        self.izq = izq
        self.der = der
        self.linea = int(linea)
      
    def Chequear(self,i,d):
        if i.tipo in [0,1] and d.tipo in [0,1]:
            if i.tipo == 0 and d.tipo == 0:
                return 0
            else:
                return 1
        elif i.tipo == 2 and d.tipo == 2:
            return 2
        else:
            raise RuntimeError("Linea %d: Incompatibilidad de tipos" % (0 if self.linea - VarGlobales.Lresta < 0 else self.linea - VarGlobales.Lresta))
        
    def Verificar_rango(self, resultado):
        if resultado < -1048576 or resultado > 1048576:
            raise RuntimeError("Linea %d: Operacion fuera del rango permitido" % (0 if self.linea - VarGlobales.Lresta < 0 else self.linea - VarGlobales.Lresta))
        return resultado
    
    def division_por_cero(self,ni,nd):
        if nd == 0:
            raise RuntimeError("Linea %d: Division por cero" % (0 if self.linea - VarGlobales.Lresta < 0 else self.linea - VarGlobales.Lresta))
        return self.Verificar_rango(ni/nd)
    
    def ejecutar(self):
        i = self.izq.ejecutar()
        if i != None and self.Op == 'y' and i.valor == False:
            return Variable(None,2,i.valor)
        elif i != None and self.Op == 'o' and i.valor == True:
            return Variable(None,2,i.valor)
        d = self.der.ejecutar()
        if i == None or d == None:
            raise RuntimeError("Linea %d: Incompatibilidad de tipos" % (0 if self.linea - VarGlobales.Lresta < 0 else self.linea - VarGlobales.Lresta))
        t = self.Chequear(i, d)
        if t == 2:
            if self.Op == 'y':
                return Variable(None,t,i.valor and d.valor)
            elif self.Op == 'o':
                return Variable(None,t,i.valor or d.valor)
            elif self.Op == '==':
                return Variable(None,2,i.valor == d.valor)
            elif self.Op == '!=':
                return Variable(None,2,i.valor != d.valor)
            else:
                raise RuntimeError("Linea %d: Operador incorrecto para los operandos" % (0 if self.linea - VarGlobales.Lresta < 0 else self.linea - VarGlobales.Lresta))
        else:
            if self.Op == '+':
                return Variable(None,t,self.Verificar_rango(i.valor + d.valor))
            elif self.Op == '-':
                return Variable(None,t,self.Verificar_rango(i.valor - d.valor))
            elif self.Op == '*':
                return Variable(None,t,self.Verificar_rango(i.valor * d.valor))
            elif self.Op == '/':
                return Variable(None,t,self.division_por_cero(i.valor, d.valor))
            elif self.Op == '<':
                return Variable(None,2,i.valor < d.valor)
            elif self.Op == '<=':
                return Variable(None,2,i.valor <= d.valor)
            elif self.Op == '>':
                return Variable(None,2,i.valor > d.valor)
            elif self.Op == '>=':
                return Variable(None,2,i.valor >= d.valor)
            elif self.Op == '==':
                return Variable(None,2,i.valor == d.valor)
            elif self.Op == '!=':
                return Variable(None,2,i.valor != d.valor)
            else:
                raise RuntimeError("Linea %d: Operador incorrecto para los operandos" % (0 if self.linea - VarGlobales.Lresta < 0 else self.linea - VarGlobales.Lresta))
    
class OpUnaria(Node):
    def __init__(self, Op, expr, linea = 0):
        self.Op = Op
        self.expr = expr
        self.linea = int(linea)
        
    def ejecutar(self):
        e = self.expr.ejecutar()
        if e == None:
            raise RuntimeError("Linea %d: expresion no valida para operacion" % (0 if self.linea - VarGlobales.Lresta < 0 else self.linea - VarGlobales.Lresta))
        if self.Op == '-':
            if e.tipo in [0,1]:
                return Variable(None, e.tipo, -e.valor)
            else:
                raise RuntimeError("Linea %d: Tipo no valido  para el operador -" % (0 if self.linea - VarGlobales.Lresta < 0 else self.linea - VarGlobales.Lresta))
        if self.Op == '!':
            if e.tipo == 2:
                return Variable(None, e.tipo, not (e.valor))
            else:
                raise RuntimeError("Linea %d: Tipo no valido  para el operador !" % (0 if self.linea - VarGlobales.Lresta < 0 else self.linea - VarGlobales.Lresta))
        if self.Op == '()':
            return Variable(None, e.tipo, e.valor)
    
class Id(Node):
    def __init__(self, nombre, linea = 0):
        self.nombre = nombre
        self.linea = int(linea)
        
    def ejecutar(self):
        if VarGlobales.PilaVariables.containVar(self.nombre):
            return VarGlobales.PilaVariables.getvalor(self.nombre)
        else:
            raise RuntimeError("Linea %d: Variable no declarada" % (0 if self.linea - VarGlobales.Lresta < 0 else self.linea - VarGlobales.Lresta))
    
class numeroEntero(Node):
    def __init__(self, numero):
        self.numero = numero
        
    def ejecutar(self):
        return Variable(None, 0, int(self.numero))
    
class numeroFlotante(Node):
    def __init__(self, numero):
        self.numero = numero
        
    def ejecutar(self):
        return Variable(None, 1, float(self.numero))
    
class Booleano(Node):
    def __init__(self, tipo):
        self.tipo = tipo
        
    def ejecutar(self):
        if self.tipo == 'falso':
            return Variable(None,2,False)
        else:
            return Variable(None,2,True)
    
############################################################################
class Variable():
    def __init__(self, id, tipo, valor):
        self.id = id
        self.tipo = tipo
        self.valor = valor
    
class Pila():
    def __init__(self):
        self.pila = [{}]
    
    def imprimirVars(self):
        l = {}
        for i in self.pila[-1].itervalues():
            l[i.id] = i.valor
        print l
        #print self.pila[-1]
        
    #solo crea el diccionario y le hace un push en la pila    
    def reservarVars(self):
        self.pila.append({})
        
    def popVars(self):
        self.pila.pop()
        
    #adiere variables al diccionario de la cima de la pila    
    def addVar(self, id, tipo, valor):
        vars = self.pila[-1]
        vars[id]=Variable(id, tipo, valor)
    
    #obtiene el valor de la cima de la pila 
    def getvalor(self, id):
        return self.pila[-1][id]
    
    def containVar(self, id):
        return self.pila[-1].has_key(id)
